﻿using Exiled.API.Features;
using PlayerRoles;
using PlayerRoles.RoleAssign;
using RgCore.GameData;
using RgCore.GameModes;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Log = Exiled.API.Features.Log;

namespace RgCore.RoleAssignment
{
    public static class RoleAssignment
    {
        public static event Action OnAssigningRole;

        public static void AssigningRole()
        {
            OnAssigningRole?.Invoke();
        }

        public enum SpawnTeam : byte
        {
            SCPs, Guards, ChaosInsurgency, Scientists, ClassD, Spectator, MtfForces
        }

        public static List<Player> StartPlayer { get; private set; } = new List<Player>();
        public static List<RoleReservation> Success = new List<RoleReservation>();
        public static List<RoleReservation> Fails = new List<RoleReservation>();
        public static int latejoin = 0;

        public static void ClearAll()
        {
            Success.Clear();
            Fails.Clear();
        }

        public static RoleTypeId ToRoleTypeId(this SpawnTeam team)
        {
            switch (team)
            {
                case SpawnTeam.ClassD: return RoleTypeId.ClassD;
                case SpawnTeam.Guards: return RoleTypeId.FacilityGuard;
                case SpawnTeam.ChaosInsurgency: return RoleTypeId.ChaosRifleman;
                case SpawnTeam.MtfForces: return RoleTypeId.NtfPrivate;
                default: return RoleTypeId.Spectator;
            }
        }

        public static SpawnTeam ToSpawnTeam(this RoleTypeId id)
        {
            var t = PlayerRolesUtils.GetTeam(id);
            if (t != Team.FoundationForces && t != Team.Dead && t != Team.OtherAlive)
            {
                return (SpawnTeam)t;
            }
            switch (id)
            {
                case RoleTypeId.FacilityGuard:
                    return SpawnTeam.Guards;

                case RoleTypeId.NtfPrivate:
                case RoleTypeId.NtfCaptain:
                case RoleTypeId.NtfSergeant:
                case RoleTypeId.NtfSpecialist:
                    return SpawnTeam.MtfForces;

                default:
                    return SpawnTeam.Spectator;
            }
        }

        public static void SpawnScps(int count)
        {
            List<RoleTypeId> allscps = GameMode.CurrentGameMode.GetScpDrawPool(count).ToList();
            count = Math.Min(count, allscps.Count);
            Dictionary<Player, RoleTypeId> scps = new Dictionary<Player, RoleTypeId>(); //入选的玩家
            Dictionary<Player, int> guarantees = new Dictionary<Player, int>(); //所有玩家
            foreach (Player player in Player.List)
            {
                if (player != null && !player.IsOverwatchEnabled)
                    guarantees.Add(player, GameplayData.GameDatas.FirstOrDefault(x => x.Player == player)?.NotScp_Con_Times ?? 0);
            }
            while (scps.Count < count)
            {
                if (GameMode.CurrentGameMode.Attribute.RoleReservationEnabled)
                {
                    var rr = RoleReservation.Reservations.FirstOrDefault
                        (x => (x.RoleType == RoleTypeId.None || x.RoleType.ToSpawnTeam() == SpawnTeam.SCPs) && guarantees.Any(y => y.Key.UserId == x.UserId)); //优先寻找预留
                    if (rr != null)
                    {
                        Player p = Player.Get(rr.UserId);
                        if (p == null) //玩家离开
                        {
                            RoleReservation.Reservations.Remove(rr);
                            continue;
                        }
                        if (rr.RoleType != RoleTypeId.None) //指定体验卡
                        {
                            if (!allscps.Contains(rr.RoleType)) //指定角色已经被分配
                            {
                                RoleReservation.Reservations.Remove(rr);
                                Fails.Add(rr);
                                continue;
                            }
                            p.RoleManager.ServerSetRole(rr.RoleType, RoleChangeReason.RoundStart, RoleSpawnFlags.All);
                            Log.Info($"玩家 {p.Nickname} 使用体验卡直接分配SCP角色");
                            allscps.Remove(rr.RoleType);
                            count--;
                        }
                        else
                        {
                            Log.Info($"玩家 {p.Nickname} 使用随机体验卡");
                            scps.Add(p, rr.RoleType); //随机体验卡
                        }
                        Success.Add(rr);
                        guarantees.Remove(p);
                        RoleReservation.Reservations.Remove(rr);
                        Log.Info($"{scps.Count} / {count}");
                        continue;
                    }
                    bool result = false;
                    int weight = guarantees.Values.Max() + 1;
                    do
                    {
                        weight--;
                        var list = guarantees.Where(x => x.Value == weight);
                        if (list.Count() > 0)
                        {
                            var player = list.ElementAt(UnityEngine.Random.Range(0, list.Count()));
                            guarantees.Remove(player.Key);
                            scps.Add(player.Key, RoleTypeId.None);
                            result = true;
                        }
                    }
                    while (!result && weight > 0);
                    if (!result)
                    {
                        Log.Error("没有足够的玩家生成指定数量的SCP，已跳过");
                        break;
                    }
                }
                else
                {
                    if (guarantees.Keys.Count == 0)
                    {
                        Log.Error("没有足够的玩家生成指定数量的SCP，已跳过");
                        break;
                    }
                    var player = guarantees.Keys.ElementAt(UnityEngine.Random.Range(0, guarantees.Keys.Count));
                    guarantees.Remove(player);
                    scps.Add(player, RoleTypeId.None);
                }
            }
            Dictionary<Player, Dictionary<RoleTypeId, int>> scp = new Dictionary<Player, Dictionary<RoleTypeId, int>>();
            foreach (var key in scps)
            {
                var preferences = new Dictionary<RoleTypeId, int>(key.Key.ScpPreferences.Preferences);
                foreach (var cls in allscps)
                {
                    if (!preferences.ContainsKey(cls))
                        preferences.Add(cls, 0);
                }
                scp.Add(key.Key, preferences);
            }
            while (scp.Count > 0 && allscps.Count > 0)
            {
                var role = allscps[UnityEngine.Random.Range(0, allscps.Count)];
                int prefer = 6;
                List<Player> list_player = new List<Player>();
                while (list_player.Count == 0 && prefer >= -5) //当前和此偏好值相同的所有玩家
                {
                    prefer--;
                    list_player.AddRange(from s in scp where s.Value[role] == prefer select s.Key);
                }
                if (list_player.Count == 0 && scps.Count > 0) //已经到-5了嘛。。
                {
                    if (scp.Count > 0)
                    {
                        list_player.AddRange(scp.Keys);
                    }
                    else
                    {
                        Log.Error($"{role} 未能生成，没有任何的偏好匹配且没有可用玩家");
                        allscps.Remove(role);
                        continue;
                    }
                }
                var cur_ply = list_player[UnityEngine.Random.Range(0, list_player.Count)];
                cur_ply.RoleManager.ServerSetRole(role, RoleChangeReason.RoundStart, RoleSpawnFlags.All);
                allscps.Remove(role);
                scp.Remove(cur_ply);
            }
        }

        public static void SpawnHumans(List<SpawnTeam> queue)
        {
            List<Player> remains = new List<Player>(Player.List.Where(x => RoleAssigner.CheckPlayer(x.ReferenceHub) && !x.IsOverwatchEnabled));
            List<RoleTypeId> ntf = new List<RoleTypeId>() { RoleTypeId.NtfCaptain, RoleTypeId.NtfSergeant, RoleTypeId.NtfSergeant, RoleTypeId.NtfSergeant };
            int num = Mathf.FloorToInt(queue.Count * 0.2f);
            int num2 = Mathf.FloorToInt((queue.Count - num) * 0.3f);
            List<RoleTypeId> ci = new List<RoleTypeId>() { };
            for (int i = 0; i < num; i++)
                ci.Add(RoleTypeId.ChaosMarauder);
            for (int j = 0; j < num2; j++)
                ci.Add(RoleTypeId.ChaosMarauder);
            for (int x = 0; queue.Count < remains.Count; x++)
            {
                queue.Add(queue[x]);
            }
            for (int a = 0; a < queue.Count && remains.Count > 0; a++)
            {
                RoleTypeId role = RoleTypeId.Spectator;
                switch (queue[a])
                {
                    case SpawnTeam.Guards:
                        role = RoleTypeId.FacilityGuard; break;
                    case SpawnTeam.ClassD:
                        role = RoleTypeId.ClassD; break;
                    case SpawnTeam.Scientists:
                        role = RoleTypeId.Scientist; break;
                }
                if (queue[a] == SpawnTeam.ChaosInsurgency)
                {
                    if (ci.Count > 0)
                    {
                        role = ci.First();
                        ci.RemoveAt(0);
                    }
                    else
                        role = RoleTypeId.ChaosRifleman;
                }
                else if (queue[a] == SpawnTeam.MtfForces)
                {
                    if (ntf.Count > 0)
                    {
                        role = ntf.First();
                        ntf.RemoveAt(0);
                    }
                    else
                        role = RoleTypeId.NtfPrivate;
                }
                Player player;
                if (GameMode.CurrentGameMode.Attribute.RoleReservationEnabled)
                {
                    var reservation = RoleReservation.Reservations.FirstOrDefault(x => x.RoleType.ToSpawnTeam() == queue[a] && remains.Any(y => y.UserId == x.UserId));
                    if (reservation == null)
                    {
                        var available = remains.Where(x
                            => !RoleReservation.Reservations.Any(y => y.UserId == x.UserId && queue.GetRange(a + 1, queue.Count - a - 1).Any(z => z == y.RoleType.ToSpawnTeam())));
                        if (!available.Any())
                        {
                            continue;
                        }
                        player = available.ElementAt(UnityEngine.Random.Range(0, available.Count()));
                        remains.Remove(player);
                    }
                    else
                    {
                        RoleReservation.Reservations.Remove(reservation);
                        player = Player.Get(reservation.UserId);
                        if (player != null)
                        {
                            Success.Add(reservation);
                            remains.Remove(player);
                        }
                        else
                        {
                            var available = remains.Where(x => !RoleReservation.Reservations.Any(y => y.UserId == x.UserId));
                            player = available.ElementAt(UnityEngine.Random.Range(0, available.Count()));
                            remains.Remove(player);
                        }
                    }
                }
                else
                {
                    player = remains[UnityEngine.Random.Range(0, remains.Count)];
                    remains.Remove(player);
                }
                player.RoleManager.ServerSetRole(role, RoleChangeReason.RoundStart, RoleSpawnFlags.All);
            }
        }
    }
}